package tools

import (
	"context"
	"gozero-microservices/common-service/rpc/internal/svc"
	"sync"
	"time"

	// Redis 6.0及以下版本：选择Go-redis v8.0及以下版本。
	"github.com/go-redis/redis/v8"
	redsync "github.com/go-redsync/redsync/v4"
	"github.com/go-redsync/redsync/v4/redis/goredis/v8"
	// Redis 7.0及以上版本：选择Go-redis v9.0及以上版本。
	// "github.com/go-redsync/redsync/v4/redis/goredis/v9"
	// "github.com/redis/go-redis/v9"
)

/*
 +----------------------------------------------------------------------
 + Title        : redis 缓存【结构体封装】
 + Author       : huwl
 + Version      : 1.0.0
 + Initial-Time : 2021-09-22
 + Last-time    : 这个文件最后修改的时间 + 修改人的名称
 + Desc         : redis 缓存 文档地址【https://redis.uptrace.dev/zh/guide/go-redis.html】
		结构体封装
			适用场景：
				中大型项目，需清晰架构
				需支持多Redis实例（如主从、不同DB）
				需依赖注入、单元测试
			优点：
				解耦：通过接口隐藏实现，业务层依赖接口而非具体实现
				可测试性：轻松Mock接口进行单元测试
				灵活扩展：支持多实例、动态配置
				生命周期可控：结构体可包含连接关闭逻辑
			缺点：
				代码量稍多（需定义结构体和方法）
				需显式传递依赖（可通过依赖注入框架简化）
 +----------------------------------------------------------------------
*/

var ctx = context.Background()

var (
	redisInstance *RedisInstance
	redisOnce     sync.Once
	redisInitErr  error
)

type RedisInstance struct {
	RedisDBClient      *redis.Client
	RedisClusterClient *redis.ClusterClient
	RedisMode          string
	// redsync 实例,用于创建分布式锁
	RsPool *redsync.Redsync
}

// redis 配置信息
type RedisConfig struct {
	Mode   string `yaml:"mode" json:"mode"`
	Single struct {
		Host     string `yaml:"host" json:"host"`
		Password string `yaml:"password" json:"password"`
		Db       uint8  `yaml:"db" json:"db"`
	}

	Replication struct {
		Host     string `yaml:"host" json:"host"`
		Password string `yaml:"password" json:"password"`
		Db       uint8  `yaml:"db" json:"db"`
	}

	Sentinel struct {
		Host       []string `yaml:"host" json:"host"`
		Password   string   `yaml:"password" json:"password"`
		MasterName string   `yaml:"master_name" json:"master_name"`
		Db         uint8    `yaml:"db" json:"db"`
	}

	Cluster struct {
		Host     []string `yaml:"host" json:"host"`
		Password string   `yaml:"password" json:"password"`
		Db       uint8    `yaml:"db" json:"db"`
	}
}

/**
 * 创建redis实例
 *
 * @return *RedisInstance, error
 * @author huwl
 */
func NewRedisInstance(svcCtx *svc.ServiceContext) (*RedisInstance, error) {
	var err error
	redisConf := &RedisConfig{}

	// ++++++++++++++++ 使用本地配置方式开始 ++++++++++++++++
	// 模式
	redisConf.Mode = svcCtx.Config.RedisConf.Mode

	switch svcCtx.Config.Env {
	case "dev":
		// 单机模式
		redisConf.Single.Host = svcCtx.Config.RedisConf.Dev.Single.Host
		redisConf.Single.Password = svcCtx.Config.RedisConf.Dev.Single.Password
		redisConf.Single.Db = svcCtx.Config.RedisConf.Dev.Single.Db

		// 主从模式
		redisConf.Replication.Host = svcCtx.Config.RedisConf.Dev.Replication.Host
		redisConf.Replication.Password = svcCtx.Config.RedisConf.Dev.Replication.Password
		redisConf.Replication.Db = svcCtx.Config.RedisConf.Dev.Replication.Db

		// 哨兵模式
		redisConf.Sentinel.Host = svcCtx.Config.RedisConf.Dev.Sentinel.Host
		redisConf.Sentinel.Password = svcCtx.Config.RedisConf.Dev.Sentinel.Password
		redisConf.Sentinel.Db = svcCtx.Config.RedisConf.Dev.Sentinel.Db

		// 分片模式
		redisConf.Cluster.Host = svcCtx.Config.RedisConf.Dev.Cluster.Host
		redisConf.Cluster.Password = svcCtx.Config.RedisConf.Dev.Cluster.Password
		redisConf.Cluster.Db = svcCtx.Config.RedisConf.Dev.Cluster.Db
	case "test":
		// 单机模式
		redisConf.Single.Host = svcCtx.Config.RedisConf.Test.Single.Host
		redisConf.Single.Password = svcCtx.Config.RedisConf.Test.Single.Password
		redisConf.Single.Db = svcCtx.Config.RedisConf.Test.Single.Db

		// 主从模式
		redisConf.Replication.Host = svcCtx.Config.RedisConf.Test.Replication.Host
		redisConf.Replication.Password = svcCtx.Config.RedisConf.Test.Replication.Password
		redisConf.Replication.Db = svcCtx.Config.RedisConf.Test.Replication.Db

		// 哨兵模式
		redisConf.Sentinel.Host = svcCtx.Config.RedisConf.Test.Sentinel.Host
		redisConf.Sentinel.Password = svcCtx.Config.RedisConf.Test.Sentinel.Password
		redisConf.Sentinel.Db = svcCtx.Config.RedisConf.Test.Sentinel.Db

		// 分片模式
		redisConf.Cluster.Host = svcCtx.Config.RedisConf.Test.Cluster.Host
		redisConf.Cluster.Password = svcCtx.Config.RedisConf.Test.Cluster.Password
		redisConf.Cluster.Db = svcCtx.Config.RedisConf.Test.Cluster.Db
	case "prod":
		// 单机模式
		redisConf.Single.Host = svcCtx.Config.RedisConf.Prod.Single.Host
		redisConf.Single.Password = svcCtx.Config.RedisConf.Prod.Single.Password
		redisConf.Single.Db = svcCtx.Config.RedisConf.Prod.Single.Db

		// 主从模式
		redisConf.Replication.Host = svcCtx.Config.RedisConf.Prod.Replication.Host
		redisConf.Replication.Password = svcCtx.Config.RedisConf.Prod.Replication.Password
		redisConf.Replication.Db = svcCtx.Config.RedisConf.Prod.Replication.Db

		// 哨兵模式
		redisConf.Sentinel.Host = svcCtx.Config.RedisConf.Prod.Sentinel.Host
		redisConf.Sentinel.Password = svcCtx.Config.RedisConf.Prod.Sentinel.Password
		redisConf.Sentinel.Db = svcCtx.Config.RedisConf.Prod.Sentinel.Db

		// 分片模式
		redisConf.Cluster.Host = svcCtx.Config.RedisConf.Prod.Cluster.Host
		redisConf.Cluster.Password = svcCtx.Config.RedisConf.Prod.Cluster.Password
		redisConf.Cluster.Db = svcCtx.Config.RedisConf.Prod.Cluster.Db
	}

	// ++++++++++++++++ 使用本地配置方式结束 ++++++++++++++++

	// ++++++++++++++++ 使用配置中心方式开始 ++++++++++++++++
	// // InitConfigCenter(RedisConfigCenter, redisConf)
	// ++++++++++++++++ 使用配置中心方式结束 ++++++++++++++++

	mode := redisConf.Mode

	redisInstance := &RedisInstance{
		RedisMode: mode,
	}

	//连接redis
	if mode == "single" { // 单机模式
		redisInstance.RedisDBClient = redis.NewClient(&redis.Options{
			Addr:     redisConf.Single.Host,
			Password: redisConf.Single.Password,
			DB:       int(redisConf.Single.Db),
		})
		_, err = redisInstance.RedisDBClient.Ping(ctx).Result()

	} else if mode == "sentinel" { // 哨兵模式
		redisInstance.RedisDBClient = redis.NewFailoverClient(&redis.FailoverOptions{
			MasterName:    redisConf.Sentinel.MasterName,
			SentinelAddrs: redisConf.Sentinel.Host,
			Password:      redisConf.Sentinel.Password,
			DB:            int(redisConf.Sentinel.Db),
		})
		_, err = redisInstance.RedisDBClient.Ping(ctx).Result()

	} else if mode == "cluster" { // 分片模式
		redisInstance.RedisClusterClient = redis.NewClusterClient(&redis.ClusterOptions{
			Addrs:    redisConf.Cluster.Host,
			Password: redisConf.Cluster.Password,
		})

		_, err = redisInstance.RedisClusterClient.Ping(ctx).Result()
	}
	if err != nil {
		return nil, err
	}

	if redisConf.Mode == "cluster" {
		pool := goredis.NewPool(redisInstance.RedisClusterClient)
		redisInstance.RsPool = redsync.New(pool)
	} else {
		pool := goredis.NewPool(redisInstance.RedisDBClient)
		redisInstance.RsPool = redsync.New(pool)
	}

	return redisInstance, nil
}

/**
 * 获取单列模式：redis实例
 *
 * @return *RedisInstance, error
 * @author huwl
 */
func GetSingleRedisInstance(svcCtx *svc.ServiceContext) (*RedisInstance, error) {
	redisOnce.Do(func() {
		redisInstance, redisInitErr = NewRedisInstance(svcCtx)
	})
	return redisInstance, redisInitErr
}

/**
 * 设置值
 *
 * @param string key ------------------------------必传
 * @param []interface{} value ---------------------必传
 * @param time.Duration expiration ----------------必传
 * @return error
 * @author huwl
 */
func (ins *RedisInstance) RedisSet(key string, value interface{}, expiration time.Duration) error {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.Set(ctx, key, value, expiration).Err()
	}

	return ins.RedisClusterClient.Set(ctx, key, value, expiration).Err()
}

/**
 * 获取值
 *
 * @param string key ------------------------------必传
 * @return string error
 * @author huwl
 */
func (ins *RedisInstance) RedisGet(key string) (string, error) {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.Get(ctx, key).Result()
	}

	return ins.RedisClusterClient.Get(ctx, key).Result()
}

/**
 * 删除
 *
 * @param string key ------------------------------必传
 * @return error
 * @author huwl
 */
func (ins *RedisInstance) RedisDel(key string) error {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.Del(ctx, key).Err()
	}

	return ins.RedisClusterClient.Del(ctx, key).Err()
}

/**
 * 左进列表
 *
 * @param string key ------------------------------必传
 * @param []interface{} values --------------------必传
 * @return string error
 * @author huwl
 */
func (ins *RedisInstance) RedisLpush(key string, values []interface{}) error {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.LPush(ctx, key, values).Err()
	}

	return ins.RedisClusterClient.LPush(ctx, key, values).Err()
}

/**
 * 左出列表
 *
 * @param string key ------------------------------必传
 * @return error
 * @author huwl
 */
func (ins *RedisInstance) RedisLpop(key string) (string, error) {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.LPop(ctx, key).Result()
	}

	return ins.RedisClusterClient.LPop(ctx, key).Result()
}

/**
 * 右进列表
 *
 * @param string key ------------------------------必传
 * @param []interface{} values --------------------必传
 * @return error
 * @author huwl
 */
func (ins *RedisInstance) RedisRpush(key string, values []interface{}) error {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.RPush(ctx, key, values...).Err()
	}

	return ins.RedisClusterClient.RPush(ctx, key, values...).Err()
}

/**
 * 右进列表
 *
 * @param string key ---------------------------------必传
 * @return string error
 * @author huwl
 */
func (ins *RedisInstance) RedisRpop(key string) (string, error) {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.RPop(ctx, key).Result()
	}

	return ins.RedisClusterClient.RPop(ctx, key).Result()
}

/**
 * geoadd
 *
 * @param string key ---------------------------------必传
 * @param string name --------------------------------必传
 * @param float64 longitude --------------------------必传
 * @param float64 latitude ---------------------------必传
 * @return string error
 * @author huwl
 */
func (ins *RedisInstance) RedisGeoadd(key, name string, longitude, latitude float64) error {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.GeoAdd(ctx, key, &redis.GeoLocation{Name: name, Longitude: longitude, Latitude: latitude}).Err()
	}

	return ins.RedisClusterClient.GeoAdd(ctx, key, &redis.GeoLocation{Name: name, Longitude: longitude, Latitude: latitude}).Err()
}

/**
 * geodist
 *
 * @param string key ---------------------------------必传
 * @param string member1 -----------------------------必传
 * @param string member2 -----------------------------必传
 * @param string unit --------------------------------必传
 * @return string error
 * @author huwl
 */
func (ins *RedisInstance) RedisGeodist(key, member1, member2, unit string) (float64, error) {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.GeoDist(ctx, key, member1, member2, unit).Result()
	}

	return ins.RedisClusterClient.GeoDist(ctx, key, member1, member2, unit).Result()
}

/**
 * georadius
 *
 * @param string key ------------------------------必传
 * @param float64 longitude -----------------------必传
 * @param float64 latitude ------------------------必传
 * @param float64 radius --------------------------必传
 * @param string unit -----------------------------必传
 * @param withCoord bool --------------------------必传
 * @param withDist bool ---------------------------必传
 * @param string sort -----------------------------必传
 * @return string error
 * @author huwl
 */
func (ins *RedisInstance) RedisGeoradius(key string, longitude, latitude, radius float64, unit string, withCoord, withDist bool, sort string) ([]redis.GeoLocation, error) {
	geoRadiusQuery := &redis.GeoRadiusQuery{
		Radius:    radius,
		Unit:      unit,
		WithCoord: withCoord,
		WithDist:  withDist,
		Sort:      sort,
		// 	WithCoord   bool
		// WithDist    bool
		// WithGeoHash bool
		// Count       int
		// // Can be ASC or DESC. Default is no sort order.
		// Sort      string
		// Store     string
		// StoreDist string
	}
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.GeoRadius(ctx, key, longitude, latitude, geoRadiusQuery).Result()
	}

	return ins.RedisClusterClient.GeoRadius(ctx, key, longitude, latitude, geoRadiusQuery).Result()
}

/**
 * 删除geo位置(geo本质就是有序集合，因此可以使用有序集合的方式删除)
 *
 * @param string key ---------------------------------必传
 * @param []interface{} members ----------------------非必传
 * @return error
 * @author huwl
 */
func (ins *RedisInstance) RedisGeoremove(key string, members ...interface{}) error {
	return ins.RedisZrem(key, members...)
}

/**
 * 有序集合删除成员
 *
 * @param string key ---------------------------------必传
 * @param []interface{} members ----------------------非必传
 * @return error
 * @author huwl
 */
func (ins *RedisInstance) RedisZrem(key string, members ...interface{}) error {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.ZRem(ctx, key, members).Err()
	}

	return ins.RedisClusterClient.ZRem(ctx, key, members).Err()
}

/**
 * 自增(Incr)
 *
 * @param string key ---------必传
 * @return int64 error
 * @author huwl
 */
func (ins *RedisInstance) RedisIncr(key string) (int64, error) {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.Incr(ctx, key).Result()
	}

	return ins.RedisClusterClient.Incr(ctx, key).Result()
}

/**
 * 自增(IncrBy)
 *
 * @param string key ---------必传
 * @param int64 value --------必传
 * @return int64 error
 * @author huwl
 */
func (ins *RedisInstance) RedisIncrBy(key string, value int64) (int64, error) {
	if ins.RedisMode == "single" || ins.RedisMode == "sentinel" {
		return ins.RedisDBClient.IncrBy(ctx, key, value).Result()
	}

	return ins.RedisClusterClient.IncrBy(ctx, key, value).Result()
}

// Close 关闭 Redis 连接
func (ins *RedisInstance) Close() error {
	if ins.RedisDBClient != nil {
		return ins.RedisDBClient.Close()
	}
	if ins.RedisClusterClient != nil {
		return ins.RedisClusterClient.Close()
	}
	return nil
}

// 待续。。。
